﻿/*  ----------------------------------------------------------------------------
 *  Microsoft Public License (http://www.microsoft.com/opensource/licenses.mspx#Ms-PL)
 *  
 *  ----------------------------------------------------------------------------
 *  DynamicAspect
 *  ----------------------------------------------------------------------------
 *  File:       WeavableObject.cs
 *  Author:     NiceWk (n9walker@hotmail.com)
 *  ----------------------------------------------------------------------------
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.Reflection;
using System.ComponentModel.Composition;
using System.Collections;

namespace Bizall.Dynamic.Aspect
{
    /// <summary>
    /// Dynamic class that weave aspect into target object internal by using decorative manner.
    /// </summary>
    /// <typeparam name="T">Target object type.</typeparam>
    public sealed class WeavableObject<T> : DynamicObject, IPartImportsSatisfiedNotification where T: class
    {
        #region fields

        AspectChain chain = new AspectChain();
         T source;
        #endregion

        #region import properties
      /// <summary>
        /// Used by MEF to import aspects.
        /// </summary>
        [ImportMany(typeof(IAspect), AllowRecomposition = true, RequiredCreationPolicy= CreationPolicy.Any)]
        public IEnumerable<IAspect> Aspects { get; set; }
        #endregion

        #region constructor
        /// <summary>
        /// Default constructor
        /// </summary>
        public WeavableObject()
        {
            source = Activator.CreateInstance<T>();
        }

        /// <summary>
        /// Overrided constructor with a parameter
        /// </summary>
        /// <param name="source">Be weaved target object</param>
        public WeavableObject(T source)
        {
            this.source = source;
        }
        #endregion

        #region public methods
        /// <summary>
        /// Overrides base method to provide a method template for weave methods that implements <see cref="IAspect"/> interface.
        /// </summary>
        /// <param name="binder"> Provides information about the dynamic operation. The binder.Name property
        /// provides the name of the member on which the dynamic operation is performed. <see cref="DynamicObject.TryInvokeMember"/></param>
        /// <param name="args"> The arguments that are passed to the object member during the invoke operation.</param>
        /// <param name="result">The result of the member invocation.</param>
        /// <returns> true if the operation is successful; otherwise, false. </returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            result = null;

            var mi = typeof(T).GetMethods().FirstOrDefault(m => m.Name == binder.Name && binder.ReturnType.IsAssignableFrom(m.ReturnType) && IsMatchParameters(m.GetParameters(), args));

            if (mi != null)
            {
                WeavingContext context = new WeavingContext(source, binder, args);

                try
                {
                    chain.BeforeMethodCall(context);

                    if (chain.HasAroundMethod(context))
                        chain.AroundMethodcall(context);
                    else
                        context.ReturnValue = mi.Invoke(source, args);
                }
                catch (Exception ex)
                {
                    ProcessException(context, ex);
                }
                finally
                {
                    chain.AfterMethodCall(context);
                    result = context.ReturnValue;
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Provides the implementation for operations that get member values. 
        /// Specifying dynamic behavior for operations such as getting a value for a  property.
        /// </summary>
        /// <param name="binder">Provides information about the object that called the dynamic operation.
        /// The binder.Name property provides the name of the member on which the dynamic
        /// operation is performed. 
        /// </param>
        /// <param name="value">The result of the get operation.</param>
        /// <returns> true if the operation is successful; otherwise, false.</returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = null;
            var property = GetProperty(binder);
            if (property != null)
            {
                WeavingContext context = new WeavingContext(source, binder, returnValue: result);
                try
                {
                    chain.BeforeGetValue(context);
                    result = property.GetValue(source, null);
                    chain.AfterGetValue(context);
                }
                catch (Exception ex)
                {
                    ProcessException(context, ex);
                }

                return true;
            }
            return false;
        }

         /// <summary>
        /// Provides the implementation for operations that set member values. 
        /// To specify dynamic behavior for operations such as setting a value for a  property.
        /// </summary>
        /// <param name="binder">Provides information about the object that called the dynamic operation.</param>
        /// <param name="value">The value to set to the member.</param>
        /// <returns>true if the operation is successful; otherwise, false. </returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var property = GetProperty(binder);
            if (property != null)
            {
                var oldValue = property.GetValue(source, null);
                if (oldValue != value)
                {
                    WeavingContext context = new WeavingContext(source, binder, returnValue: value);
                    try
                    {
                        chain.PropertyChanging(context);
                        property.SetValue(source, value, null);
                        chain.PropertyChanged(context);
                    }
                    catch (Exception ex)
                    {
                        ProcessException(context, ex);
                    }
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Add weavers that target satisfies the target object.
        /// </summary>
        /// <param name="aspect"></param>
        public void AddAspect(IAspect aspect)
        {
            if (aspect != null)
            {
                lock(((ICollection)chain).SyncRoot)
                {
                    if (chain.FirstOrDefault(c => aspect.GetType().IsAssignableFrom(c.GetType())) == null)
                        chain.AddLast(aspect);
                }
            }
        }    
        
        /// <summary>
        /// Override method to call target ToString()
        /// </summary>
        /// <returns>Return target ToString() result.</returns>
        public override string ToString()
        {
            return source.ToString();
        }
        #endregion

        #region private methods
      
        private PropertyInfo GetProperty(DynamicMetaObjectBinder binder)
        {
            var b = binder as GetMemberBinder;
            if (b != null)
                return typeof(T).GetProperties().FirstOrDefault(p => p.Name == b.Name);
            else
                return typeof(T).GetProperties().FirstOrDefault(p => p.Name == ((SetMemberBinder)binder).Name);
        }
   
       private void ProcessException(WeavingContext context, Exception ex)
        {
            var exp = new DynamciException("DynamicAspectError", ex);
            if (chain.ExceptionMethodCall(context, exp) == false)
                throw exp;
        }

        private bool IsMatchParameters(ParameterInfo[] parameterInfo, object[] args)
        {
            if (args.Length > parameterInfo.Length)
                return false;

            for (int i = 0; i < args.Length; i++)
            {
                //TODO: What about the arg is null?
                if( args[i] !=null  &&   !args[i].GetType().IsAssignableFrom(parameterInfo[i].ParameterType))
                    return false;
            }

            return true;
        } 
     #endregion

        #region IPartImportsSatisfiedNotification Members
        /// <summary>
        /// Providers implementation of <see cref="IPartImportsSatisfiedNotification"/> interface. 
        /// Called when an aspect's imports have been satisfied.
        /// </summary>
        public void OnImportsSatisfied()
        {
            var query = Aspects.Where(a => a.IsMatch(typeof(T).FullName) && a.Enabled == true).OrderBy(a => a.Sequence);
           
            foreach (var asp in query)
                     AddAspect(asp);
        }

        #endregion
    }
}